package org.apache.ocean.main.name;

import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.apache.commons.lang.StringUtils;
import org.apache.ocean.main.client.OceanXML;
import org.apache.ocean.main.http.HttpParameters;
import org.apache.ocean.main.http.HttpTimeout;
import org.apache.ocean.main.http.MulticastHttp;
import org.apache.ocean.main.http.MulticastHttp.ExceptionEnum;
import org.apache.ocean.main.util.CElement;
import org.apache.ocean.main.util.Service;
import org.apache.ocean.main.util.Timeout;
import org.apache.ocean.main.util.Util;
import org.jdom.Element;

// TODO: implement lease handling
public class NameServer implements Service {
	public static Logger log = Logger.getLogger(NameServer.class.getName());
	private String id;
	private URL url;
	private List<URL> nameUrls;
	private File filesDirectory;
	private String serviceStatus;
	private NameClient nameClient;
	private SQLNameDatabase nameDatabase;
	private ExecutorService threadPool;
	private Leases leases;
  
	public class FileScannerTask implements Runnable {
		public void run() {
			for (File file : filesDirectory.listFiles()) {
				
			}
		}
	}
	
	public class Leases {
		private Map<LeaseKey, Lease> map = new HashMap<LeaseKey, Lease>();

		public Lease renew(LeaseKey key, long duration) {
			Lease lease = map.get(key);
			if (lease == null) {
				lease = new Lease();
				map.put(key, lease);
				// TODO: sent all nodes
			}
			lease.renew(duration);
			return lease;
		}
    
		public List<LeaseKey> getKeys(Class<? extends LeaseKey> clazz) {
			List<LeaseKey> list = new ArrayList<LeaseKey>();
			for (LeaseKey leaseKey : map.keySet()) {
				if (leaseKey.getClass().isAssignableFrom(clazz)) {
					list.add(leaseKey);
				}
			}
			return list;
		}
	}

	public static class Lease {
		private long expiration;

		public boolean isExpired() {
			return System.currentTimeMillis() > expiration;
		}

		public void renew(long duration) {
			expiration = duration + System.currentTimeMillis();
		}
	}
  
	public static abstract class LeaseKey {
		private URL url;
		
		public LeaseKey(URL url) {
			this.url = url;
		}
		
		public URL getUrl() {
			return url;
		}
	}
	
	public static class CellMasterLeaseKey extends LeaseKey {
		private String cell;
		
		public CellMasterLeaseKey(String cell, URL url) {
			super(url);
			this.cell = cell;
		}
	}
	
	public static class FileEventLeaseKey extends LeaseKey {
		public FileEventLeaseKey(String globPattern, URL url) {
			super(url);
		}
	}
	
	public static class NodeEventLeaseKey extends LeaseKey {
		private String cell;
		
		public NodeEventLeaseKey(String cell, URL url) {
			super(url);
			this.cell = cell;
		}

		public String getCell() {
			return cell;
		}
	}

	public Lease registerForNodeEvents(String cell, URL url, long duration) {
		return leases.renew(new NodeEventLeaseKey(cell, url), duration);
	}
  
	public void nodesUpdated(List<Node> nodes) {
		for (Object object : leases.getKeys(NodeEventLeaseKey.class)) {
			NodeEventLeaseKey nodeEventLeaseKey = (NodeEventLeaseKey)object;
			threadPool.submit(new NodesUpdated(nodeEventLeaseKey.getUrl(), nodes));
		}
	}
	
	public void notifyNames() {
		threadPool.submit(new NotifyNames());
	}

	public File getFilesDirectory() {
		return filesDirectory;
	}

	public static HttpTimeout getDefaultHttpTimeout() {
		return new HttpTimeout(1000, 1000);
	}

	public List<Node> getCellNodes(String cell) throws Exception {
		return nameDatabase.findNodesByCell(cell);
	}

	public List<CElement> getData(long replicationId) throws Exception {
		List<CElement> results = new ArrayList<CElement>();
		results.addAll(nameDatabase.findNodes(replicationId));
		results.addAll(nameDatabase.findCells(replicationId));
		return results;
	}

	public void syncData() {
		threadPool.submit(new SyncData());
	}

	public class NodesUpdated implements Runnable {
		private List<Node> nodes;
		private URL url;

		public NodesUpdated(URL url, List<Node> nodes) {
			this.url = url;
			this.nodes = nodes;
		}

		public void run() {
			try {
				HttpTimeout httpTimeout = new HttpTimeout(1000, 2 * 1000);
				NameMergeClient.nodesUpdated(url, nodes, httpTimeout);
			} catch (Exception exception) {
				log.log(Level.SEVERE, "", exception);
			}
		}
	}

	public class SyncData implements Runnable {
		public void run() {
			try {
				Name master = nameClient.getMaster(new Timeout(5 * 1000));
				URL url = master.getUrl();
				long replicationId = getName().getHighestReplicationId();
				HttpParameters httpParameters = new HttpParameters();
				httpParameters.add("action", "getdata");
				httpParameters.add("replicationid", replicationId + 1);
				OceanXML response = OceanXML.request(url, httpParameters, getDefaultHttpTimeout());
				for (Element element : response.getBodyChildren()) {
					String name = element.getName();
					if (StringUtils.equals("node", name)) {
						Node node = new Node(element);
						nameDatabase.updateNode(node);
					} else if (StringUtils.equals("cell", name)) {
						Cell cell = new Cell(element);
						nameDatabase.updateCell(cell);
					}
				}
			} catch (Exception exception) {
				log.log(Level.SEVERE, "", exception);
			}
		}
	}

	public class NotifyNames implements Runnable {
		public void run() {
			try {
				Map<String, Name> map = nameClient.getNames();
				map.remove(id);
				MulticastHttp multicastHttp = new MulticastHttp(threadPool, ExceptionEnum.NONE);
				List<URL> urls = new ArrayList<URL>();
				for (Name name : map.values()) {
					urls.add(name.getUrl());
				}
				HttpParameters httpParameters = new HttpParameters("action", "notifyname");
				multicastHttp.execute(urls, httpParameters, getDefaultHttpTimeout());
			} catch (Exception exception) {
				log.log(Level.SEVERE, "", exception);
			}
		}
	}

	public Name getName() throws Exception {
		Long highestReplicationId = nameDatabase.findHighestReplicationId();
		Name master = nameClient.getMaster(Timeout.FOREVER());
		return new Name(id, url, master.getId(), highestReplicationId);
	}

	public Name getChosenMaster() throws Exception {
		return nameClient.getChosenMaster();
	}

	public void start() throws Exception {
		serviceStatus = Service.STARTING;
		File defaultHome = new File(Util.getAppServerHome(), "name");
		File homeDirectory = Util.getHomeDirectory("oceannamehome", defaultHome);
		homeDirectory.mkdirs();
		File databaseDirectory = new File(homeDirectory, "database");
		databaseDirectory.mkdirs();
		filesDirectory = new File(homeDirectory, "files");
		filesDirectory.mkdirs();
		nameDatabase = new SQLNameDatabase(databaseDirectory);
		threadPool = Executors.newFixedThreadPool(10);
		nameClient = new NameClient(nameUrls, null, threadPool);
		serviceStatus = Service.STARTED;
	}

	public void stop() throws Exception {
		serviceStatus = Service.STOPPING;
		threadPool.shutdown();
		serviceStatus = Service.STOPPED;
	}

	public String getServiceStatus() {
		return serviceStatus;
	}
}
